﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using Common.Utilities;

namespace Workflows.Components.TextMining.Collapse
{
    /// <summary></summary>
    public enum OrgPhraseType
    {
        /// <summary></summary>
        RareName = 0,
        /// <summary></summary>
        Address = 1,
        /// <summary></summary>
        Type = 2,
        /// <summary></summary>
        Name = 3,
        /// <summary></summary>
        School = 4,
        /// <summary></summary>
        health = 5,
        /// <summary></summary>
        Corporation = 6,
        /// <summary></summary>
        Division = 7
    }

    /// <summary>
    /// 
    /// </summary>
    public class OrgNameEncodedComparer : IAttributeComparer
    {
        private Dictionary<string, int> _StopWords;
        private Dictionary<string, string> _Synonyms;
        private Dictionary<OrgPhraseType, Dictionary<string, int>> _Tokens;
        private Dictionary<OrgPhraseType, double> _Weights;
        private Dictionary<OrgPhraseType, double> _Penalties;
        private List<string> _Symbols;
        /// <summary>
        /// </summary>
        public OrgNameEncodedComparer()
        {
            this._Symbols=new List<string>();
            this._Symbols.Add("=");
            this._Symbols.Add("+");
            this._Symbols.Add("<");
            this._Symbols.Add(">");
            this._Symbols.Add(",");
            this._Symbols.Add(".");
            this._Symbols.Add("?");
            this._Symbols.Add("/");
            this._Symbols.Add("\\");
            this._Symbols.Add("|");
            this._Symbols.Add("]");
            this._Symbols.Add("[");
            this._Symbols.Add("{");
            this._Symbols.Add("}");
            this._Symbols.Add("!");
            this._Symbols.Add("@");
            this._Symbols.Add("#");
            this._Symbols.Add("$");
            this._Symbols.Add("%");
            this._Symbols.Add("^");
            this._Symbols.Add("&");
            this._Symbols.Add("*");
            this._Symbols.Add("(");
            this._Symbols.Add(")");
            this._Symbols.Add("_");
            this._Symbols.Add("-");
            this._Symbols.Add("~");
            this._Symbols.Add("`");
            this._StopWords = new Dictionary<string, int>();
            string[] stopWords = Properties.Resources.EntityStopWords.Split(new char[] { '\n', '\r' });
            foreach (string stopWord in stopWords)
            {
                if (stopWord.Trim().Length > 0)
                    this._StopWords.Add(stopWord, this._StopWords.Count + 1);
            }

            this._Synonyms = new Dictionary<string, string>();
            string[] lines1 = Properties.Resources.EntitySynonyms.Split(new char[] { '\n', '\r' });
            foreach (string line in lines1)
            {
                if (line.Trim().Length > 0)
                {
                    string[] parts = line.Trim().ToLower().Split(new char[] { ',' });
                    string fullName = parts[0];
                    string alterName = parts[1];
                    this._Synonyms.Add(alterName, fullName);
                }
            }

            this._Tokens = new Dictionary<OrgPhraseType, Dictionary<string, int>>();
            string[] lines2 = Properties.Resources.EntityFreqs.Split(new char[] { '\n', '\r' });
            foreach (string line in lines2)
            {
                if (line.Trim().Length > 0)
                {
                    string[] parts = line.Trim().ToLower().Split(new char[] { ',' });
                    if (parts.Length == 3)
                    {
                        OrgPhraseType orgPhraseType = OrgPhraseType.RareName;
                        string phraseType = parts[0];
                        string word = parts[1];
                        if (phraseType == "address")
                        {
                            orgPhraseType = OrgPhraseType.Address;
                        }
                        else if (phraseType == "school")
                        {
                            orgPhraseType = OrgPhraseType.School;
                        }
                        else if (phraseType == "corporation")
                        {
                            orgPhraseType = OrgPhraseType.Corporation;
                        }
                        else if (phraseType == "health")
                        {
                            orgPhraseType = OrgPhraseType.health;
                        }
                        else if (phraseType == "type")
                        {
                            orgPhraseType = OrgPhraseType.Type;
                        }
                        else if (phraseType == "name")
                        {
                            orgPhraseType = OrgPhraseType.Name;
                        }
                        else if (phraseType == "division")
                        {
                            orgPhraseType = OrgPhraseType.Division;
                        }

                        if (this._Tokens.ContainsKey(orgPhraseType))
                        {
                            Dictionary<string, int> tokens = this._Tokens[orgPhraseType];
                            tokens.Add(word, tokens.Count + 1);
                            this._Tokens[orgPhraseType] = tokens;
                        }
                        else
                        {
                            Dictionary<string, int> tokens = new Dictionary<string, int>();
                            tokens.Add(word, tokens.Count + 1);
                            this._Tokens.Add(orgPhraseType, tokens);
                        }
                    }
                }
            }

            this._Weights=new Dictionary<OrgPhraseType, double>();
            this._Penalties=new Dictionary<OrgPhraseType, double>();
            string[] lines3 = Properties.Resources.PhraseTypes.Split(new char[] { '\n', '\r' });
            foreach(string line in lines3)
            {
                if(line.Trim().Length>0)
                {
                    string[] parts = line.ToLower().Split(new char[] {','});
                    if(parts.Length==3)
                    {
                        string phraseType = parts[0];
                        OrgPhraseType orgPhraseType = OrgPhraseType.RareName;
                        if (phraseType == "address")
                        {
                            orgPhraseType = OrgPhraseType.Address;
                        }
                        else if (phraseType == "school")
                        {
                            orgPhraseType = OrgPhraseType.School;
                        }
                        else if (phraseType == "corporation")
                        {
                            orgPhraseType = OrgPhraseType.Corporation;
                        }
                        else if (phraseType == "health")
                        {
                            orgPhraseType = OrgPhraseType.health;
                        }
                        else if (phraseType == "type")
                        {
                            orgPhraseType = OrgPhraseType.Type;
                        }
                        else if (phraseType == "name")
                        {
                            orgPhraseType = OrgPhraseType.Name;
                        }
                        else if (phraseType == "division")
                        {
                            orgPhraseType = OrgPhraseType.Division;
                        }
                        double weight = double.Parse(parts[1]);
                        double penalty = double.Parse(parts[2]);
                        this._Weights.Add(orgPhraseType,weight);
                        this._Penalties.Add(orgPhraseType,penalty);
                    }
                }
            }
        }

        public AttributeComparerType ComparerType
        {
            get { return AttributeComparerType.ClassComparer; }
        }

        /// <summary>
        /// 
        /// </summary>
        public SqlDbType FieldDbType
        {
            get { return SqlDbType.NVarChar; }
            set { throw new NotImplementedException(); }
        }

        public double Compare(object value1, object value2, double penalty, SqlConnection conn,
                              string unevenDistTableName)
        {
            if (value1 == null || value2 == null || value1 == DBNull.Value || value2 == DBNull.Value || 
                value1.ToString().Length == 0 || value2.ToString().Length == 0)
                return 0.0;
            string name1 = value1.ToString().ToLower().Trim();
            string name2 = value2.ToString().ToLower().Trim();
            if (name1 == name2)
                return 1.0;

            string phraseKey1 = string.Empty;
            string wordKey1 = string.Empty;
            this.EncodeOrganizationName(name1,ref phraseKey1,ref wordKey1);
            string phraseKey2 = string.Empty;
            string wordKey2 = string.Empty;
            this.EncodeOrganizationName(name2, ref phraseKey2, ref wordKey2);
            double similarityScore = this.SimilarityByEncoding(phraseKey1, wordKey1, phraseKey2, wordKey2);
            if(similarityScore<=0)
            {
                if (name1.Length < name2.Length && this.IsAcronym(name1))
                {
                    if (MatchesAcronym(name1, name2))
                        similarityScore = 0.5;
                }
                else if (name1.Length > name2.Length && this.IsAcronym(name2))
                {
                    if (this.MatchesAcronym(name2, name1))
                        similarityScore = 0.5;
                }
            }
            if (similarityScore <= 0)
                return penalty;
            else
                return Math.Min(1.0, similarityScore);
        }

        public object Merge(List<object> values, SqlConnection conn, string unevenDistTableName)
        {
            if (values == null || values.Count == 0)
                return null;

            Dictionary<object, int> countByValues = new Dictionary<object, int>();
            foreach (object obj in values)
            {
                if (obj == null || obj.ToString().Length==0)
                    continue;

                if (!countByValues.ContainsKey(obj))
                    countByValues.Add(obj, 1);
                else
                    countByValues[obj] = countByValues[obj] + 1;
            }

            if (countByValues.Count > 0)
            {
                object mode = null;
                int count = 0;
                foreach (object obj in countByValues.Keys)
                {
                    if (countByValues[obj] > count)
                    {
                        count = countByValues[obj];
                        mode = obj;
                    }
                }
                return mode;
            }
            else
                return null;
        }

        private bool IsAcronym(string input)
        {
            if(string.IsNullOrEmpty(input) || input.Length >8)
                return false;
            if(input.IndexOf(" ")>0)
                return false;
            char[] chars = input.ToCharArray();
            foreach(char c in chars)
                if(char.IsDigit(c) || char.IsPunctuation(c) || char.IsNumber(c) ||
                    char.IsSeparator(c) || char.IsWhiteSpace(c) || char.IsSymbol(c) ||
                    char.IsLower(c))
                    return false;
            return true;
        }

        private bool MatchesAcronym(string acronym, string input)
        {
            List<string> stopWords = new List<string>();
            stopWords.Add("of");
            stopWords.Add("the");
            stopWords.Add("and");
            stopWords.Add("at");
            stopWords.Add("a");
            stopWords.Add("an");
            stopWords.Add("to");
            stopWords.Add("&");
            string[] words = StringUtil.Split(input.ToLower(), " ");
            char[] letters = acronym.ToLower().ToCharArray();
            List<string> wordList=new List<string>();
            foreach(string word in words)
            {
                if(!stopWords.Contains(word))
                    wordList.Add(word);
            }
            if(wordList.Count !=letters.Length)
                return false;
            for(int i=0;i<letters.Length;i++)
            {
                if(!wordList[i].StartsWith(letters[i].ToString()))
                    return false;
            }
            return true;
        }

        private void EncodeOrganizationName(
            string orgName, ref string phraseTypeEncoded,
            ref string wordSequenceEncoded)
        {
            phraseTypeEncoded = string.Empty;
            wordSequenceEncoded = string.Empty;

            foreach (string stopWord in this._StopWords.Keys)
                orgName = orgName.Replace(" " + stopWord + " ", " ");
            foreach (string symbol in this._Symbols)
                orgName = orgName.Replace(symbol, " ");
            orgName = orgName.ToLower().Trim();
            while (orgName.IndexOf("  ") > 0)
                orgName = orgName.Replace("  ", " ");

            List<string> tokens = new List<string>();
            string[] words = orgName.Split(new char[] { ' ' });
            foreach (string word in words)
            {
                if (this._Synonyms.ContainsKey(word))
                    tokens.Add(this._Synonyms[word]);
                else
                    tokens.Add(word);
            }
            foreach (string token in tokens)
            {
                if (phraseTypeEncoded.Length > 0)
                    phraseTypeEncoded += "-";
                if (wordSequenceEncoded.Length > 0)
                    wordSequenceEncoded += "-";

                bool exist = false;
                foreach(OrgPhraseType orgPhraseType in this._Tokens.Keys)
                {
                    foreach(string word in this._Tokens[orgPhraseType].Keys)
                    {
                        if(word==token)
                        {
                            exist = true;
                            phraseTypeEncoded += ((int) orgPhraseType).ToString();
                            wordSequenceEncoded += this._Tokens[orgPhraseType][word].ToString();
                            break;
                        }
                    }
                }
                if(!exist)
                {
                    int wordIdx = 0;
                    if(this._Tokens.ContainsKey(OrgPhraseType.RareName))
                    {
                        Dictionary<string, int> rareTokens = this._Tokens[OrgPhraseType.RareName];
                        wordIdx = rareTokens.Count + 1;
                        rareTokens.Add(token, wordIdx);
                        this._Tokens[OrgPhraseType.RareName] = rareTokens;
                    }
                    else
                    {
                        Dictionary<string,int> rareTokens=new Dictionary<string, int>();
                        wordIdx = rareTokens.Count + 1;
                        rareTokens.Add(token, wordIdx);
                        this._Tokens.Add(OrgPhraseType.RareName, rareTokens);
                    }
                    phraseTypeEncoded += ((int) OrgPhraseType.RareName).ToString();
                    wordSequenceEncoded += wordIdx.ToString();
                }
            }
        }

        private double SimilarityByEncoding(string phraseKey1, string wordKey1,
            string phraseKey2, string wordKey2)
        {
            double score = 0;

            string commonPhraseKey = string.Empty;
            int phraseSubLen = StringUtil.LongestCommonSubsequence(phraseKey1, phraseKey2, ref commonPhraseKey);
            while (commonPhraseKey.StartsWith("-"))
                commonPhraseKey = commonPhraseKey.Substring(1);
            while (commonPhraseKey.EndsWith("-"))
                commonPhraseKey = commonPhraseKey.Substring(0, commonPhraseKey.Length - 1);
            string commonWordKey = string.Empty;
            int wordSubLen = StringUtil.LongestCommonSubsequence(wordKey1, wordKey2, ref commonWordKey);
            while (commonWordKey.StartsWith("-"))
                commonWordKey = commonWordKey.Substring(1);
            while (commonWordKey.EndsWith("-"))
                commonWordKey = commonWordKey.Substring(0, commonWordKey.Length - 1);
            if (phraseSubLen > 0 && wordSubLen > 0)
            {
                string[] commonPhrases = commonPhraseKey.Split(new char[] { '-' });
                string[] commonWords = commonWordKey.Split(new char[] {'-'});
                string[] words1 = wordKey1.Split(new char[] { '-' });
                string[] words2 = wordKey2.Split(new char[] { '-' });
                string[] phrases1 = phraseKey1.Split(new char[] { '-' });
                string[] phrases2 = phraseKey2.Split(new char[] { '-' });
                foreach (string commonPhrase in commonPhrases)
                {
                    OrgPhraseType orgPhraseType = (OrgPhraseType) int.Parse(commonPhrase);
                    List<string> commonWords1 = new List<string>();
                    List<string> commonWords2 = new List<string>();
                    for (int i = 0; i < phrases1.Length; i++)
                    {
                        if (phrases1[i] == commonPhrase)
                        {
                            if (!commonWords1.Contains(words1[i]))
                                commonWords1.Add(words1[i]);
                        }
                    }
                    for (int i = 0; i < phrases2.Length; i++)
                    {
                        if (phrases2[i] == commonPhrase)
                            if (!commonWords2.Contains(words2[i]))
                                commonWords2.Add(words2[i]);
                    }
                    string[] sharedWords = StringUtil.Intersection(commonWords1.ToArray(), commonWords2.ToArray());
                    if (sharedWords != null && sharedWords.Length > 0)
                    {
                        score += this._Weights[orgPhraseType]*((double) sharedWords.Length)/
                                 (Math.Max(commonWords1.Count, commonWords2.Count));
                    }
                }
                List<string> commonWordList=new List<string>();
                commonWordList.AddRange(commonWords);
                foreach(string word1 in words1)
                {
                    if (!commonWordList.Contains(word1))
                        score -= 0.2;
                }
                foreach (string word2 in words2)
                {
                    if (!commonWordList.Contains(word2))
                        score -= 0.2;
                }

                double penalty1 = 0;
                foreach (string phrase1 in phrases1)
                {
                    OrgPhraseType orgPhraseType = (OrgPhraseType)int.Parse(phrase1);
                    bool shared = false;
                    foreach (string commonPhrase in commonPhrases)
                    {
                        if (commonPhrase == phrase1)
                        {
                            shared = true;
                            break;
                        }
                    }
                    if (!shared)
                        penalty1 += this._Penalties[orgPhraseType];
                }
                double penalty2 = 0;
                foreach (string phrase2 in phrases2)
                {
                    OrgPhraseType orgPhraseType = (OrgPhraseType)int.Parse(phrase2);
                    bool shared = false;
                    foreach (string commonPhrase in commonPhrases)
                    {
                        if (commonPhrase == phrase2)
                        {
                            shared = true;
                            break;
                        }
                    }
                    if (!shared)
                        penalty2 += this._Penalties[orgPhraseType];
                }
                foreach(string commonPhrase in commonPhrases)
                {
                    bool contains1 = false;
                    foreach(string phrase1 in phrases1)
                    {
                        if(phrase1==commonPhrase)
                        {
                            contains1 = true;
                            break;
                        }
                    }
                    bool contains2 = false;
                    foreach (string phrase2 in phrases2)
                    {
                        if (phrase2 == commonPhrase)
                        {
                            contains2 = true;
                            break;
                        }
                    }
                    if(contains1 && !contains2)
                    {
                        score -= 0.1;
                    }
                    else if (contains2 && !contains1)
                    {
                        score -= 0.1;
                    }
                }
                double penalty = Math.Min(penalty1, penalty2);
                return score + penalty;
            }
            else
                return 0;
        }
    }
}
